﻿using System;
using System.Data;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using RegexLib.Web.Models.Data;
using System.Text;
using System.Security.Cryptography;

namespace RegexLib.Web.Models.Web
{
	/// <summary>
	/// 用于为成员资格提供相应的功能。
	/// </summary>
	public static class RxMembership
	{
		/// <summary>
		/// 判断在当前HTTP上下文中是否有用户已经登录。
		/// </summary>
		/// <returns>用户是否已经登录。</returns>
		public static bool IsUserLoggedIn()
		{
			return HttpContext.Current.User != null
				&& !string.IsNullOrEmpty(HttpContext.Current.User.Identity.Name);
		}

		/// <summary>
		/// 从当前上下文中获取已登录的用户。
		/// </summary>
		/// <returns>已登录的用户。如果用户尚未登录，返回null。</returns>
		public static UserInfo GetCurrentUser()
		{
			if (!IsUserLoggedIn())
				return null;

			Guid id = new Guid(HttpContext.Current.User.Identity.Name);
			using (RxDataContext db = new RxDataContext())
			{
				return db.UserInfos.FirstOrDefault(u => u.UserId == id);
			}
		}

		/// <summary>
		/// 申请一个新用户，为其发送邮件并等待确认。
		/// </summary>
		/// <param name="securityEmail">用户的登录Email。</param>
		/// <param name="userIP">用户申请时的IP地址。</param>
		/// <exception cref="RxMemberAlreadyExistsException">
		/// 如果具有指定邮箱（不区分大小写）的用户已经存在，则抛出此异常。
		/// </exception>
		public static void ApplyUser(string securityEmail, string userIP)
		{
			if (securityEmail == null)
				throw new ArgumentNullException("securityEmail");

			string email = securityEmail.Trim();
			if (string.IsNullOrEmpty(email) || !email.IsEmail())
				throw new ArgumentException("Email地址格式不正确。", "securityEmail");

			string loweredEmail = email.ToLower();

			// 将用户保存到数据库中。
			using (RxDataContext db = new RxDataContext())
			{
				var qu = db.UserInfos.FirstOrDefault(u => u.LoweredSecurityEmail == loweredEmail);

				if (qu != null)
					throw new RxMemberAlreadyExistsException();

				var user = db.PendingUsers.FirstOrDefault(u => u.LoweredSecurityEmail == loweredEmail);

				if(user == null)
				{
					// 如果用户不存在，创建新的申请。
					user = new PendingUser();
					user.SecurityEmail = email;
					user.LoweredSecurityEmail = loweredEmail;

					db.PendingUsers.InsertOnSubmit(user);
				}

				user.ApplyCode = Guid.NewGuid();
				user.ApplyTime = DateTime.Now;
				user.UserIP = userIP;

				// 向用户发送电子邮件。
				RxEmailTemplate template = RxEmailTemplate.Register;
				template.FormatBody(RxUrl.GetRegisterUrl(email, user.ApplyCode));
				RxMailUtility.SendMailTemplate(template, user.SecurityEmail);

				db.SubmitChanges();
			}
		}

		/// <summary>
		/// 检查用户申请是否存在，以及验证码是否匹配。
		/// </summary>
		/// <param name="email">用户Email。</param>
		/// <param name="code">申请码。</param>
		/// <exception cref="RxMemberNotExistException">
		/// 如果用户Email不存在，则抛出此异常。
		/// </exception>
		/// <exception cref="RxInvalidVerifyCodeException">
		/// 如果验证码与用户Email不匹配，则抛出此异常。
		/// </exception>
		public static void CheckApply(string email, Guid code)
		{
			using (RxDataContext db = new RxDataContext())
			{
				var user = db.PendingUsers.FirstOrDefault(u => u.LoweredSecurityEmail == email.ToLower());

				if (user == null)
					throw new RxMemberNotExistException();

				if (user.ApplyCode != code)
					throw new RxInvalidVerifyCodeException();
			}
		}

		/// <summary>
		/// 注册一个用户。
		/// </summary>
		/// <param name="nickname">昵称。</param>
		/// <param name="securityEmail">电子邮件地址。</param>
		/// <param name="password">密码。</param>
		/// <param name="userIP">注册时的IP地址。</param>
		/// <returns>刚刚注册的用户。</returns>
		/// <exception cref="RxMemberAlreadyExistsException">
		/// 如果具有给定邮箱（不区分大小写）的用户已存在，则引发该异常。
		/// </exception>
		public static UserInfo RegisterUser(
			string nickname,
			string securityEmail,
			string accessKey,
			string password,
			string userIP)
		{
			UserInfo user = new UserInfo();
			user.UserId = Guid.NewGuid();
			user.Nickname = nickname;
			user.SecurityEmail = securityEmail;
			user.AccessKey = accessKey;
			user.LoweredSecurityEmail = securityEmail.ToLower();
			user.PasswordSalt = GenerateSalt();
			user.PasswordHash = EncodePassword(password, user.PasswordSalt);
			user.RegisterTime = DateTime.Now;
			user.RegisterIP = userIP ?? "";
			user.LastLoginTime = user.RegisterTime;
			user.LastLoginIP = user.RegisterIP;
			user.LastPasswordChangeTime = user.RegisterTime;
			user.LastPasswordChangeIP = user.RegisterIP;

			using (RxDataContext db = new RxDataContext())
			{
				var q = db.UserInfos.FirstOrDefault(u => u.LoweredSecurityEmail == user.LoweredSecurityEmail);

				if (q != null)
					throw new RxMemberAlreadyExistsException();

				var qa = db.UserInfos.FirstOrDefault(u => u.AccessKey == user.AccessKey);
				if (qa != null)
					throw new RxAccessKeyAlreadyExistsException();

				db.UserInfos.InsertOnSubmit(user);
				db.SubmitChanges();
			}

			DeletePendingUser(user.LoweredSecurityEmail);

			return user;
		}

		/// <summary>
		/// 尝试登录。
		/// </summary>
		/// <param name="securityEmail">用户注册时提供的电子邮件地址。</param>
		/// <param name="password">密码。</param>
		/// <param name="userIP">登录时的IP地址。</param>
		/// <returns>如果登录成功，返回正在登录的用户对象；否则会抛出异常。</returns>
		/// <exception cref="RxMemberNotExistException">
		/// 如果尝试登录的用户不存在，则抛出此异常。
		/// </exception>
		/// <exception cref="RxInvalidPasswordException">
		/// 如果密码错误，则抛出此异常。
		/// </exception>
		public static UserInfo TryLogin(string securityEmail, string password, string userIP)
		{
			using (RxDataContext db = new RxDataContext())
			{
				// 检查用户是否存在。
				var user = db.UserInfos.FirstOrDefault(u => u.LoweredSecurityEmail == securityEmail.ToLower());

				if (user == null)
					throw new RxMemberNotExistException();

				// 检查密码是否正确。
				if (user.PasswordHash != EncodePassword(password, user.PasswordSalt))
					throw new RxInvalidPasswordException();

				// 更新登录时间等信息。
				user.LastLoginTime = DateTime.Now;
				user.LastLoginIP = userIP ?? "";

				db.SubmitChanges();

				return user;
			}
		}

		/// <summary>
		/// 申请修改密码，为用户发送确认信。
		/// </summary>
		/// <param name="securityEmail">用户的注册Email。</param>
		/// <param name="userIP">用户发送申请时的IP地址。</param>
		/// <exception cref="RxMemberNotExistException">
		/// 如果具有指定email的用户不存在，则抛出此异常。
		/// </exception>
		public static void ApplyChangePassword(string securityEmail, string userIP)
		{
			if (securityEmail == null)
				throw new ArgumentNullException("securityEmail");

			string email = securityEmail.Trim();
			if (string.IsNullOrEmpty(email) || !email.IsEmail())
				throw new ArgumentException("Email地址格式不正确。", "securityEmail");

			string loweredEmail = email.ToLower();

			using (RxDataContext db = new RxDataContext())
			{
				// 找到正在申请的用户
				var qUser = db.UserInfos.FirstOrDefault(u => u.LoweredSecurityEmail == loweredEmail);

				if (qUser == null)
					throw new RxMemberNotExistException();

				// 创建申请
				var qPendingChangePassword = db.PendingPasswords.FirstOrDefault(p => p.UserId == qUser.UserId);

				if(qPendingChangePassword == null)
				{
					// 如果不存在，创建新的申请。
					qPendingChangePassword = new PendingPassword();
					qPendingChangePassword.UserId = qUser.UserId;

					db.PendingPasswords.InsertOnSubmit(qPendingChangePassword);
				}

				qPendingChangePassword.ApplyTime = DateTime.Now;
				qPendingChangePassword.ApplyCode = Guid.NewGuid();
				qPendingChangePassword.UserIP = userIP ?? "";

				// 向用户发送电子邮件。
				RxEmailTemplate template = RxEmailTemplate.ChangePassword;
				template.FormatBody(RxUrl.GetChangePasswordUrl(qPendingChangePassword.UserId, qPendingChangePassword.ApplyCode));
				RxMailUtility.SendMailTemplate(template, qUser.SecurityEmail);

				db.SubmitChanges();
			}
		}

		/// <summary>
		/// 检查修改密码的申请是否存在，以及验证码是否匹配。
		/// </summary>
		/// <param name="userId">用户ID。</param>
		/// <param name="code">验证码。</param>
		/// <exception cref="RxMemberNotExistException">
		/// 如果用户不存在，则抛出此异常。
		/// </exception>
		/// <exception cref="RxInvalidVerifyCodeException">
		/// 如果验证码与用户ID不匹配，则抛出此异常。
		/// </exception>
		public static void CheckPasswordApply(Guid userId, Guid code)
		{
			using (RxDataContext db = new RxDataContext())
			{
				var q = db.PendingPasswords.FirstOrDefault(p => p.UserId == userId);

				if (q == null)
					throw new RxMemberNotExistException();

				if (q.ApplyCode != code)
					throw new RxInvalidVerifyCodeException();
			}
		}

		/// <summary>
		/// 重置用户密码。
		/// </summary>
		/// <param name="userId">用户ID。</param>
		/// <param name="newPassword">新密码。</param>
		/// <param name="userIP">修改密码时的IP地址。</param>
		public static void ResetPassword(Guid userId, string newPassword, string userIP)
		{
			if (string.IsNullOrEmpty(newPassword))
				throw new ArgumentNullException("newPassword");

			using (RxDataContext db = new RxDataContext())
			{
				// 查找指定用户
				var qUser = db.UserInfos.FirstOrDefault(u => u.UserId == userId);

				if (qUser == null)
					throw new RxMemberNotExistException();

				// 修改密码
				qUser.PasswordSalt = GenerateSalt();
				qUser.PasswordHash = EncodePassword(newPassword, qUser.PasswordSalt);
				qUser.LastPasswordChangeTime = DateTime.Now;
				qUser.LastPasswordChangeIP = userIP ?? "";

				// 删除密码修改申请
				try
				{
					var q = db.PendingPasswords.First(p => p.UserId == qUser.UserId);
					db.PendingPasswords.DeleteOnSubmit(q);
				}
				catch
				{ }

				db.SubmitChanges();
			}
		}

		/// <summary>
		/// 删除用户的申请。
		/// </summary>
		/// <param name="loweredSecurityEmail">小写的用户Email。</param>
		private static void DeletePendingUser(string loweredSecurityEmail)
		{
			try
			{
				using (RxDataContext db = new RxDataContext())
				{
					var q = db.PendingUsers.First(u => u.LoweredSecurityEmail == loweredSecurityEmail);

					db.PendingUsers.DeleteOnSubmit(q);
					db.SubmitChanges();
				}
			}
			catch
			{
			}
		}

		/// <summary>
		/// 用于对密码进行散列的算法名字。
		/// </summary>
		public const string PasswordHashAlgorithmName = "SHA1";

		/// <summary>
		/// 使用给定的Salt值对密码进行散列。
		/// </summary>
		/// <param name="password">密码。</param>
		/// <param name="salt">Salt值。</param>
		/// <returns>经过散列的密码。</returns>
		static string EncodePassword(string password, string salt)
		{
			byte[] src = Encoding.Unicode.GetBytes(password);
			byte[] saltbuf = Convert.FromBase64String(salt);
			byte[] dst = new byte[saltbuf.Length + src.Length];
			byte[] inArray = null;
			Buffer.BlockCopy(saltbuf, 0, dst, 0, saltbuf.Length);
			Buffer.BlockCopy(src, 0, dst, saltbuf.Length, src.Length);

			HashAlgorithm algorithm = HashAlgorithm.Create(PasswordHashAlgorithmName);
			inArray = algorithm.ComputeHash(dst);

			return Convert.ToBase64String(inArray);
		}

		/// <summary>
		/// 生成随机的Salt值。
		/// </summary>
		/// <returns>表示Salt值的base64字符串。</returns>
		static string GenerateSalt()
		{
			byte[] data = new byte[0x10];
			new RNGCryptoServiceProvider().GetBytes(data);
			return Convert.ToBase64String(data);
		}
	}

	/// <summary>
	/// 表示用户已经存在。
	/// </summary>
	public class RxMemberAlreadyExistsException : Exception
	{
	}

	/// <summary>
	/// 表示访问路径已被使用。
	/// </summary>
	public class RxAccessKeyAlreadyExistsException : Exception
	{
	}

	/// <summary>
	/// 表示用户不存在。
	/// </summary>
	public class RxMemberNotExistException : Exception
	{
	}

	/// <summary>
	/// 表示密码错误。
	/// </summary>
	public class RxInvalidPasswordException : Exception
	{
	}

	/// <summary>
	/// 表示验证码错误。
	/// </summary>
	public class RxInvalidVerifyCodeException : Exception
	{
	}
}
